package conf2.model;

import conf2.model.EActor.Side;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class WorldState
{

    private Map<EActor, ETerritory> whereabouts = new EnumMap<EActor, ETerritory>(EActor.class);
    private Map<ETerritory, List<EActor>> regionContents = new EnumMap<ETerritory, List<EActor>>(ETerritory.class);
    private boolean isWatcherRevealed = false;

    public Map<ETerritory, List<EActor>> getPlacementMap()
    {
        return regionContents;
    }

    public boolean isWatcherRevealed()
    {
        return isWatcherRevealed;
    }

    void setWatcherRevealed(boolean b)
    {
        isWatcherRevealed = b;
    }

    public WorldState()
    {
        for (ETerritory et : ETerritory.values()) {
            regionContents.put(et, new LinkedList<EActor>());
        }
    }

    public int countLiving(Side side)
    {
        int c = 0;

        for (EActor ea : whereabouts.keySet()) {
            if (ea.getSide() == side) {
                c++;
            }
        }

        return c;
    }

    public List<EActor> remainingActors(Side side)
    {
        List<EActor> lst = new LinkedList<EActor>();
        for (EActor ea : whereabouts.keySet()) {
            if (ea.getSide() == side) {
                lst.add(ea);
            }
        }
        return lst;
    }

    public ETerritory whereIs(EActor ea)
    {
        return whereabouts.get(ea);
    }

    public boolean isAlive(EActor ea)
    {
        return whereabouts.get(ea) != null;
    }

    public boolean isFull(ETerritory et)
    {
        switch (et) {
            case MORDOR:
            case SHIRE:
                return regionContents.get(et).size() == 4;
            case MISTYMTS:
            case HIGHPASS:
            case MORIA:
            case GAPOFROHAN:
                return regionContents.get(et).size() == 1;
            default:
                return regionContents.get(et).size() == 2;
        }
    }

    public boolean isEmpty(ETerritory et)
    {
        return getOccupantCount(et) == 0;
    }

    public Side getOccupyingSide(ETerritory et)
    {
        if (isEmpty(et)) {
            return null;
        }
        return regionContents.get(et).get(0).getSide();
    }

    public int getOccupantCount(ETerritory et)
    {
        return regionContents.get(et).size();
    }

    public boolean isOccupiedBy(ETerritory et, Side side)
    {
        return getOccupyingSide(et) == side;
    }

    public List<ETerritory> getSidewaysRetreatOptions(EActor who)
    {
        if (who == null) {
            throw new NullPointerException();
        }
        if (!isAlive(who)) {
            throw new AssertionError("Actor " + who + " is not in the world!");
        }

        ETerritory where = whereabouts.get(who);
        ETerritory[] options = WorldMap.getSideways(where);
        return processRetreatOptions(options, who);
    }

    public List<ETerritory> getBackwardsRetreatOptions(EActor who)
    {
        if (who == null) {
            throw new NullPointerException();
        }
        if (!isAlive(who)) {
            throw new AssertionError("Actor " + who + " is not in the world!");
        }

        ETerritory where = whereabouts.get(who);
        ETerritory[] options;
        if (who.isLight()) {
            options = WorldMap.getLightBackwards(where);
        } else {
            options = WorldMap.getDarkBackwards(where);
        }
        return processRetreatOptions(options, who);
    }

    public List<ETerritory> getForwardRetreatOptions(EActor who)
    {
        if (who == null) {
            throw new NullPointerException();
        }
        if (!isAlive(who)) {
            throw new AssertionError("Actor " + who + " is not in the world!");
        }

        ETerritory where = whereabouts.get(who);
        ETerritory[] options;
        if (who.isLight()) {
            options = WorldMap.getLightForward(where);
        } else {
            options = WorldMap.getDarkForward(where);
        }
        return processRetreatOptions(options, who);
    }

    private List<ETerritory> processRetreatOptions(ETerritory[] allOptions, EActor forWhom)
    {
        List<ETerritory> result = new ArrayList<ETerritory>();
        for (ETerritory et : allOptions) {
            boolean available = isEmpty(et) || (isOccupiedBy(et, forWhom.getSide()) && !isFull(et));
            if (available) {
                result.add(et);
            }
        }
        return result;
    }

    public boolean isValidAttack(EActor attacker, EActor victim)
    {
        return victim.getSide() == attacker.getOpposingSide() &&
                isValidMove(attacker, whereIs(victim));
    }

    public boolean isValidMove(EActor who, ETerritory to)
    {
        if (who == null || to == null) {
            throw new NullPointerException();
        }
        if (!isAlive(who)) {
            throw new AssertionError("Actor " + who + " is not in the world!");
        }

        return MoveLogic.isValidMove(this, who, to);
    }

    public void placeActor(EActor who, ETerritory where)
    {

        ETerritory curr = whereIs(who);
        if (isFull(where) || getOccupyingSide(where) == who.getOpposingSide()) {
            throw new AssertionError("Can't place " + who + " in " + where + ": region is full!");
        }

        if (curr != null) {
            regionContents.get(curr).remove(who);
        }
        regionContents.get(where).add(who);
        whereabouts.put(who, where);
    }

    public void removeActor(EActor who)
    {
        whereabouts.remove(who);

        ETerritory et = whereabouts.get(who);
        if (et == null) {
            System.out.println("removeActor: got null as region for " + who);
            return;
        }

        regionContents.get(et).remove(who);
    }

    /**
     * NOTE: This does not consider the fact that the card may already have been
     * played. That is for another module to keep track of.
     * @param c
     * @return
     */
    public boolean canPlaySpecialCard(ESpecialCard c)
    {
        switch (c) {
            case A_KING_REVEALED:
                return SpecialCardLogic.canPlayAKingRevealed(this);
            case CREBAIN_OF_DUNLAND:
                return SpecialCardLogic.canPlayCrebainOfDunland(this);
            case GANDALF_THE_WHITE:
                return SpecialCardLogic.canPlayGandalfTheWhite(this);
            case GWAIHIR_THE_WINDLORD:
                return SpecialCardLogic.canPlayGwaihirTheWindlord(this);
            case PALANTIR:
                return SpecialCardLogic.canPlayPalantir(this);
            case RECALL_TO_MORDOR:
                return SpecialCardLogic.canPlayRecallToMordor(this);
            case SHADOWFAX:
                return SpecialCardLogic.canPlayShadowfax(this);
            case THE_DARK_OF_MORDOR:
                return SpecialCardLogic.canPlayTheDarkOfMordor(this);
            default:
                throw new RuntimeException("Unkown special card: " + c);
        }
    }

    public List<EActor> getOccupants(ETerritory et)
    {
        return regionContents.get(et);
    }

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder("WORLDSTATE: ");

        for (ETerritory et : regionContents.keySet()) {
            sb.append(et + ": " + regionContents.get(et));
            sb.append("\n");
        }
        sb.append("Is watcher revealed? ");
        sb.append(isWatcherRevealed);
        sb.append("\n");

        return sb.toString();

    }

//    public static void main(String[] args) {
//        WorldState ws = new WorldState();
//        for (ETerritory et : ETerritory.values()) {
//            System.out.println(et);
//            ws.placeActor(EActor.FRODO, et);
//            System.out.println("\t Light: " + ws.getForwardRetreatOptions(EActor.FRODO));
//            ws.removeActor(EActor.FRODO);
//            ws.placeActor(EActor.CAVETROLL, et);
//            System.out.println("\t Dark: " + ws.getForwardRetreatOptions(EActor.CAVETROLL));
//            ws.removeActor(EActor.CAVETROLL);
//
//
//            System.out.println();
//        }
//    }
}
